// Magic Software, Inc.
// http://www.magic-software.com
// Copyright (c) 2000, All Rights Reserved
//
// Source code from Magic Software is supplied under the terms of a license
// agreement and may not be copied or disclosed except in accordance with the
// terms of that agreement.  The various license agreements may be found at
// the Magic Software web site.  This file is subject to the license
//
// FREE SOURCE CODE
// http://www.magic-software.com/License.html/free.pdf

#include "TestTerrain.h"

TestTerrain g_kTheApp("Test Terrain",640,480,MgcColor(0.5,0.0,1.0));

//----------------------------------------------------------------------------
TestTerrain::TestTerrain (char* acWindowTitle, int iWidth, int iHeight,
    const MgcColor& rkBackgroundColor)
    :
    MgcApplication(acWindowTitle,iWidth,iHeight,rkBackgroundColor)
{
    m_fHeightAboveTerrain = 2.0;
}
//----------------------------------------------------------------------------
void TestTerrain::CreateTerrain ()
{
    // It is known that the image is 256-by-256.  This fact is hard-coded
    // into this block of code.  The image is grey scale.  Use one of the
    // channels to generate heights.
    MgcImage* pkImage = new MgcImage("terrain.mif");
    unsigned char* aucData = pkImage->GetData();

    unsigned short usSize = 129;
    unsigned short* ausHeight = new unsigned short[usSize*usSize];
    memset(ausHeight,0,usSize*usSize*sizeof(unsigned short));
    for (unsigned short usY = 0; usY < 128; usY++)
    {
        for (unsigned short usX = 0; usX < 128; usX++)
        {
            unsigned short usHIndex = usX + usSize*usY;
            unsigned int uiDIndex = 6*(usX + pkImage->GetWidth()*usY);
            ausHeight[usHIndex] = 3*((unsigned short)aucData[uiDIndex]);
        }
    }

    MgcVector2 kOrigin = MgcVector2::ZERO;
    MgcReal fMinElevation = 0.0;
    MgcReal fMaxElevation = 255.0;
    MgcReal fSpacing = 1.0;

    // create the terrain
    m_spkPage = new MgcTerrainPage(usSize,ausHeight,kOrigin,fMinElevation,
        fMaxElevation,fSpacing);
    m_spkScene->AttachChild(m_spkPage);

    // color remap
    MgcReal fFrac;
    int iQuantity = (int)pkImage->GetQuantity();
    for (int i = 0, i0 = 0, i1 = 1, i2 = 2; i < iQuantity; i++)
    {
        if ( aucData[i0] < 8 )
        {
            // blue
            fFrac = aucData[i0]/7.0;
            aucData[i0] = 0;
            aucData[i1] = 0;
            aucData[i2] = (unsigned char)(192.0+(255.0-192.0)*fFrac);
        }
        else if ( aucData[i0] < 72 )
        {
            // yellow
            fFrac = (aucData[i0] - 8.0)/64.0;
            aucData[i0] = (unsigned char)(128.0+(255.0-128.0)*fFrac);
            aucData[i1] = aucData[i0];
            aucData[i2] = 0;
        }
        else if ( aucData[i0] < 128 )
        {
            // green
            fFrac = (aucData[i0] - 72.0)/56.0;
            aucData[i0] = 0;
            aucData[i1] = (unsigned char)(128.0+(255.0-128.0)*fFrac);
            aucData[i2] = 0;
        }
        else if ( aucData[i0] < 192 )
        {
            // brown
            fFrac = (aucData[i0] - 128.0)/64.0;
            aucData[i0] = (unsigned char)(64.0+(128.0-64.0)*fFrac);
            aucData[i1] = aucData[i0];
            aucData[i2] = 32;
        }
        else
        {
            // grey
            fFrac = (aucData[i0] - 192.0)/64.0;
            aucData[i0] = (unsigned char)(192.0+(255.0-192.0)*fFrac);
            aucData[i1] = aucData[i0];
            aucData[i2] = aucData[i0];
        }

        i0 += 3;
        i1 += 3;
        i2 += 3;
    }

    // create the terrain texture
    MgcTexture* pkTexture = new MgcTexture;
    pkTexture->SetImage(pkImage);
    pkTexture->Filter() = MgcTexture::FM_LINEAR;
    pkTexture->Mipmap() = MgcTexture::MM_LINEAR_LINEAR;
    MgcTextureState* pkTextureState = new MgcTextureState;
    pkTextureState->Set(0,pkTexture);
    m_spkScene->SetRenderState(pkTextureState);
}
//---------------------------------------------------------------------------
bool TestTerrain::OnInitialize ()
{
    if ( !MgcApplication::OnInitialize() )
        return false;

    // set up camera
    ms_spkCamera->SetFrustum(1.0,1000.0,-0.55,0.55,0.4125,-0.4125);

    // create root of scene graph
    MgcStream kStream;
    m_spkScene = new MgcNode(1);
    m_spkWireframeState = new MgcWireframeState;
    m_spkScene->SetRenderState(m_spkWireframeState);
    m_spkZBufferState = new MgcZBufferState;
    m_spkZBufferState->Enabled() = true;
    m_spkZBufferState->Writeable() = true;
    m_spkZBufferState->Compare() = MgcZBufferState::CF_LEQUAL;
    m_spkScene->SetRenderState(m_spkZBufferState);

    // create terrain heights and textures
    CreateTerrain();

    // position the camera in the middle of the page
    MgcReal fHeight = m_spkPage->GetHeight(MgcVector2(64.0,64.0));
    MgcVector3 kCLoc(64.0,64.0,fHeight + m_fHeightAboveTerrain);
    MgcVector3 kCLeft(0.0,-1.0,0.0);
    MgcVector3 kCUp(0.0,0.0,1.0);
    MgcVector3 kCDir(-1.0,0,0.0);
    ms_spkCamera->SetFrame(kCLoc,kCLeft,kCUp,kCDir);
    ms_spkCamera->Update();

    Simplify();
    m_spkScene->UpdateGS(0.0);
    m_spkScene->UpdateRS();

    m_bTurretActive = true;
    SetTurretAxes();
    m_fTrnSpeed = 0.05;
    m_fRotSpeed = 0.05;

    return true;
}
//----------------------------------------------------------------------------
void TestTerrain::OnTerminate ()
{
    m_spkPage = 0;
    m_spkWireframeState = 0;
    m_spkZBufferState = 0;
    m_spkScene = 0;

    MgcApplication::OnTerminate();
}
//---------------------------------------------------------------------------
void TestTerrain::DrawStatistics (int iX, int iY)
{
    if ( ms_spkRenderer )
    {
        char acMessage[256];
        sprintf_s(acMessage,"vertices = %u  triangles = %u",
            m_spkPage->GetVertexQuantity(),m_spkPage->GetTriangleQuantity());
        ms_spkRenderer->Draw(iX,iY,acMessage);
    }
}
//---------------------------------------------------------------------------
void TestTerrain::OnIdle ()
{
	// port this to BasicDirectX and render using DX 9 / 11 (DXUT) -- should just need to recode the Renderer for DXUT ???
    MeasureTime();

    MoveCamera();

    ms_spkRenderer->ClearBuffers();
    ms_spkRenderer->Draw(m_spkScene);
    ms_spkRenderer->DisplayBackBuffer();
    DrawFrameRate(8,472);
    DrawStatistics(128,472);

    UpdateClicks();
}
//---------------------------------------------------------------------------
void TestTerrain::OnKeyPress (unsigned char ucKey, int iX, int iY)
{
    if ( ucKey == 'q' || ucKey == 'Q' || ucKey == KEY_ESCAPE )
    {
        RequestTermination();
        return;
    }

    switch ( ucKey )
    {
    case '0':  // reset frame rate measurements
        ResetTime();
        return;
    case 'w':
        m_spkWireframeState->Enabled() = !m_spkWireframeState->Enabled();
        return;
    case 's':
        Simplify();
        return;
    case '+':
    case '=':
        m_spkPage->SetPixelTolerance(ms_spkRenderer,
            m_spkPage->GetPixelTolerance() + 1.0);
        Simplify();
        return;
    case '-':
    case '_':
        if ( m_spkPage->GetPixelTolerance() > 1.0 )
        {
            m_spkPage->SetPixelTolerance(ms_spkRenderer,
                m_spkPage->GetPixelTolerance() - 1.0);
            Simplify();
        }
        return;
    }
}
//----------------------------------------------------------------------------
void TestTerrain::MoveForward ()
{
    MgcApplication::MoveForward();

    MgcVector3 kLoc = ms_spkCamera->GetLocation();
    MgcReal fHeight = m_spkPage->GetHeight(MgcVector2(kLoc.x,kLoc.y));
    kLoc.z = fHeight + m_fHeightAboveTerrain;
    ms_spkCamera->SetLocation(kLoc);
    ms_spkCamera->Update();

    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::MoveBackward ()
{
    MgcApplication::MoveBackward();

    MgcVector3 kLoc = ms_spkCamera->GetLocation();
    MgcReal fHeight = m_spkPage->GetHeight(MgcVector2(kLoc.x,kLoc.y));
    kLoc.z = fHeight + m_fHeightAboveTerrain;
    ms_spkCamera->SetLocation(kLoc);
    ms_spkCamera->Update();

    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::MoveDown ()
{
    if ( m_fHeightAboveTerrain >= 1.0 )
        m_fHeightAboveTerrain -= 1.0;

    MgcVector3 kLoc = ms_spkCamera->GetLocation();
    MgcReal fHeight = m_spkPage->GetHeight(MgcVector2(kLoc.x,kLoc.y));
    kLoc.z = fHeight + m_fHeightAboveTerrain;
    ms_spkCamera->SetLocation(kLoc);
    ms_spkCamera->Update();

    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::MoveUp ()
{
    m_fHeightAboveTerrain += 1.0;

    MgcVector3 kLoc = ms_spkCamera->GetLocation();
    MgcReal fHeight = m_spkPage->GetHeight(MgcVector2(kLoc.x,kLoc.y));
    kLoc.z = fHeight + m_fHeightAboveTerrain;
    ms_spkCamera->SetLocation(kLoc);
    ms_spkCamera->Update();

    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::TurnLeft ()
{
    MgcApplication::TurnLeft();
    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::TurnRight ()
{
    MgcApplication::TurnRight();
    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::LookUp ()
{
    MgcApplication::LookUp();
    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::LookDown ()
{
    MgcApplication::LookDown();
    Simplify();
}
//---------------------------------------------------------------------------
void TestTerrain::Simplify ()
{
    // initialize the page
    m_spkPage->ResetBlocks();

    // camera location in terrain model space
    const MgcVector3& rkWorldEye = ms_spkCamera->GetLocation();
    MgcVector3 kDiff = rkWorldEye - m_spkPage->WorldTranslate();
    MgcReal fInvScale = 1.0/m_spkPage->WorldScale();
    MgcVector3 kModelEye = fInvScale*(kDiff*m_spkPage->WorldRotate());

    // camera direction in terrain model space
    const MgcVector3& rkWorldDir = ms_spkCamera->GetDirection();
    MgcVector3 kModelDir = rkWorldDir*m_spkPage->WorldRotate();

    bool bCloseAssumption = false;
    m_spkPage->Simplify(ms_spkRenderer,kModelEye,kModelDir,bCloseAssumption);
}
//---------------------------------------------------------------------------
